﻿using System;
using System.Diagnostics;
using System.Linq;
using System.Linq.Dynamic.Core;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using PasteLoggerBoard.redismodel;
using PasteLoggerBoard.serilogmodels;
using PasteLoggerBoard.usermodels;
using RabbitMQ.Client;
using RabbitMQ.Client.Events;

namespace PasteLoggerBoard.handler
{

    /// <summary>
    /// 
    /// </summary>
    public class SerilogRabbitConsumer : IHostedService
    {
        /// <summary>
        /// 
        /// </summary>
        private readonly ILogger<SerilogRabbitConsumer> _logger;
        private readonly RabbitConfig _config;
        private readonly IServiceProvider _serviceProvider;
        private readonly IHttpClientFactory _httpClientFactory;
        private readonly IAppCache _appCache;
        //private readonly ChannelHelper _channelHelper;

        /// <summary>
        /// 
        /// </summary>
        /// <param name="logger"></param>
        /// <param name="config"></param>
        /// <param name="serviceProvider"></param>
        /// <param name="appCache"></param>
        /// <param name="httpClientFactory"></param>
        public SerilogRabbitConsumer(
            ILogger<SerilogRabbitConsumer> logger,
            IOptions<RabbitConfig> config,
            IServiceProvider serviceProvider,
            IAppCache appCache,
            //ChannelHelper channelHelper,
            IHttpClientFactory httpClientFactory)
        {
            _logger = logger;
            _config = config.Value;
            _serviceProvider = serviceProvider;
            _appCache = appCache;
            _httpClientFactory = httpClientFactory;
            //_channelHelper = channelHelper;
        }

        private IConnection conn;
        private IModel channel;

        /// <summary>
        /// 
        /// </summary>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public Task StartAsync(CancellationToken cancellationToken)
        {
            //_listdata = new List<HistoryDate>();

            //  Start(); 先注释，看看异常等是如何触发的
            InstanceSystem();

            StartRabbit();

            return Task.CompletedTask;
        }

        /// <summary>
        /// 初始化系统
        /// </summary>
        private void InstanceSystem()
        {
            using var _dbContext = _serviceProvider.GetRequiredService<PasteLoggerBoardDbContext>();
            if (_dbContext.Database.GetPendingMigrations().Any())
            {
                _dbContext.Database.Migrate();
            }

            var find_admin = _dbContext.UserInfo.Where(x => x.Email == "admin@admin.com").Select(x => x.Id).FirstOrDefault();
            if (find_admin == 0)
            {
                var _role = new RoleInfo { IsEnable = true, Level = 0, Model = "root", Name = "root", Desc = "超级权限", Role = "root" };
                _dbContext.Add(_role);
                var _grade = new GradeInfo { IsEnable = true, Name = "超级管理员", Desc = "最高权限角色" };
                _dbContext.Add(_grade);
                _dbContext.SaveChanges();

                var _bind = new GradeRole { GradeId = _grade.Id, RoleId = _role.Id };
                _dbContext.Add(_bind);

                var _user = new UserInfo { Email = "admin@admin.com", PassWord = "123456".ToMd5Lower(), CreateDate = DateTime.Now, Desc = "", Grade = "root", GradeId = _grade.Id, IsEnable = true, UserHead = "", UserName = "超级管理员" };
                _dbContext.Add(_user);
                _dbContext.SaveChanges();

            }

            _dbContext.Dispose();
        }

        /// <summary>
        /// 
        /// </summary>
        private void StartRabbit()
        {
            var stopwatch = new Stopwatch();
            stopwatch.Start();
            IConnectionFactory connFactory = new ConnectionFactory//创建连接工厂对象
            {
                HostName = _config.HostName,//IP地址
                Port = _config.Port,//端口号
                UserName = _config.UserName,//用户账号
                Password = _config.Password,
                VirtualHost = _config.VirtualHost,//用户密码
                ClientProvidedName = _config.ClientName
            };
            conn = connFactory.CreateConnection();

            channel = conn.CreateModel();

            var _exchangeName = _config.Exchange;
            var _exchangeType = _config.ExchangeType;
            var _durable = true;
            var _routingKey = _config.WatchKey;
            var _queueName = _config.QueueName;

            //声明交换机
            channel.ExchangeDeclare(exchange: _exchangeName, type: _exchangeType, durable: _durable);
            //消息队列名称
            //String queueName = "loginfo";

            //多参数的支持，比如设定一个私信等！
            //var idic = new Dictionary<string, object>();
            //idic.Add("arg1","arg2");
            //channel.QueueDeclare(queue: _config.QueueName, durable: _config.Durable, exclusive: false, autoDelete: false, arguments: idic);

            //声明队列
            channel.QueueDeclare(queue: _queueName, durable: _durable, exclusive: false, autoDelete: false, arguments: null);
            //将队列与交换机进行绑定
            channel.QueueBind(queue: _queueName, exchange: _exchangeName, routingKey: _routingKey);
            //声明为手动确认 一次处理五条消息
            channel.BasicQos(0, 1, false);

            //定义消费者
            var consumer = new EventingBasicConsumer(channel);
            consumer.Received += Consumer_Received;
            consumer.ConsumerCancelled += Consumer_ConsumerCancelled;
            consumer.Registered += Consumer_Registered;
            consumer.Shutdown += Consumer_Shutdown;

            //consumer.Received += (object sender, BasicDeliverEventArgs e) =>
            //{

            //};

            channel.ModelShutdown += Channel_ModelShutdown;
            channel.CallbackException += Channel_CallbackException;

            //开启监听
            channel.BasicConsume(queue: _queueName, autoAck: false, consumer: consumer);

            stopwatch.Stop();
            _logger.LogWarning($"Init Rabbit Revicer Duration {stopwatch.Elapsed.TotalMilliseconds}ms ");

        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Channel_CallbackException(object sender, CallbackExceptionEventArgs e)
        {
            _logger.LogWarning("Channel_CallbackException");
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Channel_ModelShutdown(object sender, ShutdownEventArgs e)
        {
            _logger.LogWarning("Channel_ModelShutdown");
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Consumer_Shutdown(object sender, ShutdownEventArgs e)
        {
            _logger.LogWarning("Consumer_Shutdown");
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Consumer_Registered(object sender, ConsumerEventArgs e)
        {
            _logger.LogWarning("Consumer_Registered");
        }

        /// <summary>
        /// 
        /// </summary>ps
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Consumer_ConsumerCancelled(object sender, ConsumerEventArgs e)
        {
            _logger.LogWarning("Consumer_ConsumerCancelled");
        }

        //private IPasteLoggerBoardDbContext _dbContext = null;
        //private long _LastDispose = 0;

        /// <summary>
        /// 处理队列中的日志消息
        /// 这里貌似可以修改成批量操作，比如10条一次
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private async void Consumer_Received(object sender, BasicDeliverEventArgs e)
        {
            try
            {
                var msgbtye = e.Body;
                var msg = Encoding.UTF8.GetString(msgbtye.ToArray());
                //这里根据e.RoutingKey 执行不一样的处理逻辑
                var model = Newtonsoft.Json.JsonConvert.DeserializeObject<LoggerModel>(msg);
                //关于通知告警机制 通知人 日报告 错误 异常 AppNames(外表联动) 推送次数？
                var _now = DateTime.Now;
                var _nowtime = _now.ToUnixTimeSeconds();
                JsonElement protery = new JsonElement();
                var _message = "";
                if (model.Properties == null) { model.Properties = new PropertiesModel { }; }
                if (model.Properties != null)
                {
                    _message = Newtonsoft.Json.JsonConvert.SerializeObject(model.Properties);
                    protery = JsonDocument.Parse(_message).RootElement;
                }
                using var _scope = _serviceProvider.CreateScope();
                using var _dbContext = _scope.ServiceProvider.GetRequiredService<IPasteLoggerBoardDbContext>();
                var logdate = DateTime.Now;
                long logtime = 0;
                var _appname = model.Properties.AppName;
                if (!String.IsNullOrEmpty(model.Timestamp))
                {
                    //2023-10-13T15:31:26.5103585+08:00
                    if (model.Timestamp.Length > 19)
                    {
                        DateTime.TryParse(model.Timestamp.Substring(0, 19).Replace("T", " "), out logdate);
                    }
                    logtime = logdate.ToUnixTimeSeconds();
                }
                var _needpush = false;

                #region 日志信息按照对应的分类入库
                switch (model.Level)
                {
                    case "Warning":
                        {
                            var one = new LogWarning
                            {
                                AppName = model.Properties?.AppName,
                                Level = model.Level,
                                DateLog = logdate,
                                MessageTemplate = model.MessageTemplate,
                                Properties = protery,
                                ClientIp = model.Properties?.ClientIP.AutoSubString(32),
                                ConnectionId = model.Properties?.ConnectionId.AutoSubString(32),
                                ElapsedMilliseconds = model.Properties.ElapsedMilliseconds,
                                Path = model.Properties?.Path.AutoSubString(128),
                                RequestId = model.Properties?.RequestId.AutoSubString(32),
                                StatusCode = model.Properties.StatusCode,
                                Host = model.Properties?.Host.AutoSubString(64),
                                UserAgent = model.Properties?.UserAgent.AutoSubString(128)
                            };
                            _dbContext.Add(one);
                            _message = one.Path + model.MessageTemplate;
                            await _dbContext.SaveChangesAsync();
                        }
                        break;
                    case "Error":
                        {
                            _needpush = true;
                            var one = new LogError
                            {
                                AppName = model.Properties?.AppName,
                                Level = model.Level,
                                DateLog = logdate,
                                MessageTemplate = model.MessageTemplate,
                                Properties = protery,
                                ClientIp = model.Properties?.ClientIP.AutoSubString(32),
                                ConnectionId = model.Properties?.ConnectionId.AutoSubString(32),
                                ElapsedMilliseconds = model.Properties.ElapsedMilliseconds,
                                Path = model.Properties?.Path.AutoSubString(128),
                                RequestId = model.Properties?.RequestId.AutoSubString(32),
                                StatusCode = model.Properties.StatusCode,
                                Exception = model.Exception,
                                Host = model.Properties?.Host.AutoSubString(64),
                                UserAgent = model.Properties?.UserAgent.AutoSubString(128)
                            };
                            _dbContext.Add(one);
                            _message = one.MessageTemplate+ one.Path + model.Exception;
                            await _dbContext.SaveChangesAsync();
                            if (!String.IsNullOrEmpty(one.Path))
                            {
                                var paths = new string[] { "/api/app/Tool/TargetPoint", "/api/openuser/Image" };
                                if (paths.Contains(one.Path))
                                {
                                    _needpush = false;
                                }
                                else
                                {
                                    if (_message.Contains("Unexpected end of request content") || _message.Contains("The input is not a valid Base-64 string as it contains"))
                                    {
                                        _needpush = false;
                                    }
                                }
                            }
                        }
                        break;
                    case "Exception":
                        {
                            _needpush = true;
                            var one = new LogException
                            {
                                AppName = model.Properties?.AppName,
                                Level = model.Level,
                                DateLog = logdate,
                                MessageTemplate = model.MessageTemplate,
                                Properties = protery,
                                ClientIp = model.Properties?.ClientIP.AutoSubString(32),
                                ConnectionId = model.Properties?.ConnectionId.AutoSubString(32),
                                ElapsedMilliseconds = model.Properties.ElapsedMilliseconds,
                                Path = model.Properties?.Path.AutoSubString(128),
                                RequestId = model.Properties?.RequestId.AutoSubString(32),
                                StatusCode = model.Properties.StatusCode,
                                Exception = model.Exception,
                                Host = model.Properties?.Host.AutoSubString(64),
                                UserAgent = model.Properties?.UserAgent.AutoSubString(128)
                            };
                            _dbContext.Add(one);
                            _message = one.MessageTemplate + one.Path + model.Exception;
                            await _dbContext.SaveChangesAsync();
                            if (!String.IsNullOrEmpty(one.Path))
                            {
                                var paths =new string[]{ "/api/app/Tool/TargetPoint", "/api/openuser/Image" };
                                if (paths.Contains(one.Path))
                                {
                                    _needpush = false;
                                }
                            }
                        }
                        break;
                    case "Information":
                        {
                            var one = new LogInfo
                            {
                                AppName = model.Properties?.AppName,
                                Level = model.Level,
                                DateLog = logdate,
                                MessageTemplate = model.MessageTemplate,
                                Properties = protery,
                                ClientIp = model.Properties?.ClientIP.AutoSubString(32),
                                ConnectionId = model.Properties?.ConnectionId.AutoSubString(32),
                                ElapsedMilliseconds = model.Properties.ElapsedMilliseconds,
                                Path = model.Properties?.Path.AutoSubString(128),
                                RequestId = model.Properties?.RequestId.AutoSubString(32),
                                StatusCode = model.Properties.StatusCode,
                                Host = model.Properties?.Host.AutoSubString(64),
                                UserAgent = model.Properties?.UserAgent.AutoSubString(128)
                            };
                            if (model.MessageTemplate != null)
                            {
                                if (!model.MessageTemplate.StartsWith("Executed DbCommand")
                                 && !model.MessageTemplate.StartsWith("CORS")
                                 && !model.MessageTemplate.StartsWith("Executing")
                                 && !model.MessageTemplate.StartsWith("End Processing")
                                 && !model.MessageTemplate.StartsWith("Start processing")
                                 && !model.MessageTemplate.StartsWith("Route matched")
                                 && !model.MessageTemplate.StartsWith("Request origin")
                                 && !model.MessageTemplate.StartsWith("Received HTTP response")
                                 && !model.MessageTemplate.StartsWith("Sending")
                                 && !model.MessageTemplate.StartsWith("Entity")
                                 && !model.MessageTemplate.StartsWith("{HostingRequestStartingLog:l}")
                                 && !model.MessageTemplate.StartsWith("{HostingRequestFinishedLog:l}"))
                                {
                                    _dbContext.Add(one);
                                    await _dbContext.SaveChangesAsync();
                                }
                            }
                        }
                        break;
                    default:
                        {
                            var one = new LogInfo
                            {
                                AppName = model.Properties?.AppName,
                                Level = model.Level,
                                DateLog = logdate,
                                MessageTemplate = model.MessageTemplate,
                                Properties = protery,
                                ClientIp = model.Properties?.ClientIP.AutoSubString(32),
                                ConnectionId = model.Properties?.ConnectionId.AutoSubString(32),
                                ElapsedMilliseconds = model.Properties.ElapsedMilliseconds,
                                Path = model.Properties?.Path.AutoSubString(128),
                                RequestId = model.Properties?.RequestId.AutoSubString(32),
                                StatusCode = model.Properties.StatusCode,
                                Host = model.Properties?.Host.AutoSubString(64),
                                UserAgent = model.Properties?.UserAgent.AutoSubString(128)
                            };
                            if (model.MessageTemplate != null)
                            {
                                if (!model.MessageTemplate.StartsWith("Executed DbCommand")
                                 && !model.MessageTemplate.StartsWith("CORS")
                                 && !model.MessageTemplate.StartsWith("Executing")
                                 && !model.MessageTemplate.StartsWith("End Processing")
                                 && !model.MessageTemplate.StartsWith("Start processing")
                                 && !model.MessageTemplate.StartsWith("Route matched")
                                 && !model.MessageTemplate.StartsWith("Request origin")
                                 && !model.MessageTemplate.StartsWith("Received HTTP response")
                                 && !model.MessageTemplate.StartsWith("Sending")
                                 && !model.MessageTemplate.StartsWith("Entity")
                                 && !model.MessageTemplate.StartsWith("{HostingRequestStartingLog:l}")
                                 && !model.MessageTemplate.StartsWith("{HostingRequestFinishedLog:l}"))
                                {
                                    _dbContext.Add(one);
                                    await _dbContext.SaveChangesAsync();
                                }
                            }
                        }
                        break;
                }
                #endregion
                if (model.Properties != null)
                {
                    if (!String.IsNullOrEmpty(model.Properties.AppName))
                    {
                        CollectPoint(_now, model.Properties.AppName, model.Level.ToString(), PublicString.CollectLevel);
                        if (model.Properties.StatusCode != 0)
                        {
                            CollectPoint(_now, model.Properties.AppName, model.Properties.StatusCode.ToString(), PublicString.CollectStatus);
                        }
                        if (!String.IsNullOrEmpty(model.Properties.ClientIP))
                        {
                            CollectPoint(_now, model.Properties.AppName, model.Properties.ClientIP, PublicString.CollectClientIp);
                        }
                        if (!String.IsNullOrEmpty(model.Properties.Path))
                        {
                            CollectPoint(_now, model.Properties.AppName, model.Properties.Path, PublicString.CollectPath);
                        }
                    }
                }
                if (_needpush && model != null)
                {
                    if (!String.IsNullOrEmpty(_appname))
                    {
                        await PushErrorMessage(_dbContext, _appname, _message);
                    }
                }

                _dbContext.Dispose();
                _scope.Dispose();
            }
            catch (Exception exl)
            {
                _logger.LogException(exl);
            }
            finally
            {

                //回复服务端这个消息被处理了，是否支持批量回复
                channel.BasicAck(e.DeliveryTag, false);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="_now"></param>
        /// <param name="appname"></param>
        /// <param name="obj"></param>
        /// <param name="type"></param>
        private void CollectPoint(DateTime _now, string appname, string obj, string type)
        {
            var hour = _now.ToString("HH");
            var day = _now.ToString("yyyyMMdd");
            _appCache.HashIncrement($"{type}{day}", $"{appname}_{hour}_{obj}");
        }

        /// <summary>
        /// 把错误信息等推送到对应的消息地址中
        /// </summary>
        /// <param name="_dbContext"></param>
        /// <param name="appname"></param>
        /// <param name="_message"></param>
        /// <returns></returns>
        private async Task<bool> PushErrorMessage(IPasteLoggerBoardDbContext _dbContext, string appname, string _message)
        {
            if (!String.IsNullOrEmpty(_message))
            {
                if (_dbContext != null)
                {
                    var _query_revices = from a in _dbContext.WebHookLink
                                         join b in _dbContext.WebHooker on a.Id equals b.Id
                                         into c
                                         from d in c.DefaultIfEmpty()
                                         select new
                                         {
                                             appname = a.AppName,
                                             webhook = d != null ? d : null,
                                             isenable = a.IsEnable,
                                             level = a.Level
                                         };
                    var query_revies = await _query_revices.Where(x => x.appname == appname && x.isenable && x.level == "Error").AsNoTracking().ToListAsync();
                    if (query_revies != null && query_revies.Count > 0)
                    {
                        var _messagebody = $"{appname} {_message}".AutoSubString(1024);
                        foreach (var item in query_revies)
                        {
                            if (item.webhook != null)
                            {
                                if (item.webhook.IsEnable)
                                {
                                    if (!String.IsNullOrEmpty(item.webhook.Url))
                                    {
                                        await PushNoticeMessage(item.webhook.Url, _messagebody);
                                    }
                                }
                            }
                        }
                    }
                }
            }
            return true;
        }

        /// <summary>
        /// 推送给用户 等待
        /// </summary>
        /// <param name="url"></param>
        /// <param name="message"></param>
        private async Task<bool> PushNoticeMessage(string url, string message)
        {
            try
            {
                var json = new { msgtype = "markdown", msg_type = "markdown", content = new { title = "PasteSerilog通知", text = $"[通知]{message}" }, markdown = new { title = "PasteSerilog通知", content = $"[通知]{message}", text = $"{message}" } };
                var postdata = (Newtonsoft.Json.JsonConvert.SerializeObject(json));
                using (HttpContent httpcontent = new StringContent(postdata))
                {
                    httpcontent.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json;charset=utf-8");
                    var client = _httpClientFactory.CreateClient();
                    client.Timeout = TimeSpan.FromSeconds(4);
                    var response = await client.PostAsync(url, httpcontent);
                    if (response.IsSuccessStatusCode)
                    {
                        return true;
                    }
                    else
                    {
                        _logger.LogError(await response.Content.ReadAsStringAsync());
                        return false;
                    }
                }
            }
            catch (Exception exl)
            {
                _logger.LogException(exl);
            }
            return false;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public Task StopAsync(CancellationToken cancellationToken)
        {
            if (channel != null)
            {
                channel.Dispose();
            }
            if (conn != null)
            {
                conn.Dispose();
            }
            return Task.CompletedTask;
        }
    }

}
